8.2 回溯引用匹配

我们在解决匹配 HTML 标题问题之前先来看一个比较简单的例子,这个问题如果不使用回溯引用将无法解决。

假设你有一段文本,你想把这段文本里所有连续重复出现的单词(打字错误,其中有一个单词输了两遍)找出来。显然,在搜索某个单词的第二次出现时,这个单词必须是已知的。回溯引用允许正则表达式模式引用前面的匹配结果(具体到这个例子,就是前面匹配到的单词)。

8.2.1 匹配重复的单词

把这个问题弄明白的最佳办法是看看它到底是如何工作的。下面是一段包含着 3 组重复单词的文本,它们就是我们要找的东西:

总共找到了 10 个匹配, <h([1-6])> 匹配任何一级标题的开始标签,但我们这次用 ()[1-6] 括了起来,使它成为了一个子表达式。这样一来,我们就可以在用来匹配标题结束标签的 </h\1>\1 来引用这个子表达式了。子表达式 ([1-6]) 匹配数字 1~6, \1 只匹配与之相同的数字。这样一来,原始文本里的 <h2>这是一个错误的标题</h3> 就不会被匹配到了。

警告

回溯引用只能用来引用模式里的子表达式(用(和)括起来的正则表达式片段)。

提示

回溯引用匹配通常从 1 开始计数( \1\2 ,等等)。在许多实现里,第 0 个匹配( \0 )可以用来代表整个正则表达式。

8.2.3 使用命名捕获

正如看到的那样,子表达式是通过它们的相对位置来引用的: \1 对应着第 1 个子表达式, \5 对应着第 5 个子表达式,等等。

虽然受到普遍的支持,但这种语法存在着一个严重的不足:如果子表达式的相对位置发生了变化,整个模式也许就不能再完成原来的工作,删除或添加子表达式的后果可能更为严重。

为了弥补这一不足,一些比较新的正则表达式实现还支持命名捕获(named capture):给某个子表达式起一个唯一的名字,然后用这个名字(而不是相对位置)来引用这个子表达式。看下面这个例子:

在这个模式里面,我们把 ([1-6]) 改为了 (?<level>[1-6]) 、把 \1 改成了 \k<level> 。其中的 ?<level> 表示把子表达式 ([1-6]) 命名成 level, \k<level> 表示引用前面定义的 level 子表达式。这个使用命名捕获就不会由于前端的子表达式数量变动而需要改成后面所有的引用下标,又增加的模式的可读性。

警告

命名捕获在一些较老的正则表达式实现里面并不支持。在后端使用该特性前一定要在环境中测试后再使用。在前端使用时,如果你的网站受众可能会使用低版本的浏览器,请不要使用该特性,虽然可以配置 Webpack 等打包工具把 Javascript 转译到低版本,但正则表达式是不会被转译的,且在打包时不会报任何错误,但在低版本浏览器加载时会直接抛出一个语法错误导致应用崩溃。

发表评论